<?php
namespace App\Common\Cla\Com;

use App\Common as com;
use App\Common\Cla\Com\Token as nameToken;

class Token extends com\Cla\ClaBase {

	/**
	 * accessToken默认的生存时间
	 * @var int
	 */
	public static int $defaultExpire = 7200;

	/**
	 * 当前accessToken(实例化时需初始化)
	 * @var string
	 */
	private string $accessToken;

	/**
	 * Token缓存键对象(使用时初始化，调用方法：$this->getTokenKeyDbObj())
	 * @var com\Lib\Cache\KeyDB
	 */
	private com\Lib\Cache\KeyDB $accessTokenKeyDbObj;

	/**
	 * 当前 accessToken 数据id对象(使用时初始化，调用方法：$this->dataId())
	 * @var nameToken\DataId
	 */
	private nameToken\DataId $dataIdObj;

	/**
	 * 当前 accessToken 数据对象(使用时初始化，调用方法：$this->data())
	 * @var nameToken\Data
	 */
	private nameToken\Data $dataObj;

	/**
	 * 权限对象(使用时初始化，调用方法：$this->auth())
	 * @var nameToken\Auth
	 */
	private nameToken\Auth $authObj;

	/**
	 * session对象(使用时初始化，调用方法：$this->session())
	 * @var nameToken\Session
	 */
	private nameToken\Session $sessionObj;

	/**
	 * ApiToken constructor.
	 * @param string $accessToken 64位 accessToken
	 */
	public function __construct( string $accessToken ) {
		$this->accessToken = $accessToken;
		//
		parent::__construct();
	}

	/**
	 * 获取Token缓存键对象
	 * @return com\Lib\Cache\KeyDB
	 */
	public function getAccessTokenKeyDbObj(  ):com\Lib\Cache\KeyDB {
		if( ! isset( $this->accessTokenKeyDbObj ) ){
			$this->accessTokenKeyDbObj = com\Lib\Cache\KeyDB::use( 'sys.open.accessToken' )->param( $this->accessToken );
		}
		return $this->accessTokenKeyDbObj;
	}

	/**
	 * 获取当前的 accessToken
	 * @return string
	 */
	public function getAccessToken(  ):string {
		return $this->accessToken;
	}

	/**
	 * 检查 accessToken 是否有效
	 * @return bool [true]有效 [false]无效
	 */
	public function isValid(  ):bool {
		return $this->getAccessTokenKeyDbObj()->has();
	}

	/**
	 * 刷新 accessToken (旧 accessToken 保留10分钟)
	 * @param int $expire 新 accessToken 有效时间
	 * @return static 返回 accessToken 对象
	 * @throws
	 */
	public function refreshToken( int $expire = 7200 ):static {
		// ...
		if( $expire <= 0 ) throw com\Lib\Err\Exception::makeOfNew( '', com\Lib\Err\Code::$sysFunParamError );
		// 检查状态
		if( $this->data()->getOfPath( 'status' ) != nameToken\Code::$varStatus_Useing ){
			throw com\Lib\Err\Exception::makeOfNew( '', com\Lib\Err\Code::$sysDataError );
		}
		// 注销当前 accessToken
		$this->unset( 600 ); // 保留10分钟
		// 申请新的 accessToken
		$newTokenObj = self::apply( [
			'expire' => $expire,
			'dataId' => $this->data()->getDataId(),
		] );
		// 更新dataId中的 accessToken
		// $this->dataId()->setToken( $newTokenObj->getTokenStr() );
		// 刷新
		$newTokenObj->refreshTtl();
		//
		return $newTokenObj;
	}

	/**
	 * 注销数据，刷新数据时间
	 * @param int $delayTime 延迟销毁时间。
	 * @return void
	 */
	public function unset( int $delayTime = 1 ):void {
		// ...
		if( $delayTime < 1 ) $delayTime = 1;
		// 更新为回收状态
		$this->data()->setOfPath( 'status', nameToken\Code::$varStatus_UseingOfUnset );
		// 刷新TTL
		$this->refreshTtl( $delayTime );
	}

	/**
	 * 重置数据
	 * @return static
	 * @throws com\Lib\Err\BaseException\Throw_
	 */
	public function resetData(  ):static {
		$param = [
			'accessToken' => $this->getAccessToken(),
			'expire' => $this->ttl(),
		];
		//
		$this->del();
		// 申请
		self::apply( $param );
		//
		return $this;
	}

	/**
	 * 命令以秒为单位返回 key 的剩余过期时间。
	 * @return int 剩余过期时间。[-2]key不存在时 [-1]key 存在但没有设置剩余生存时间 [>=0]剩余过期时间
	 * @throws
	 */
	public function ttl(  ):int {
		return $this->data()->ttl();
	}

	/**
	 * 刷新数据时间
	 * @param int|null $expire 有效时间。[null]跟随主 accessToken 剩余时间 [<=0]删除
	 * @return static
	 */
	public function refreshTtl( int|null $expire = null ):static {
		// 设置数据
		if( $expire !== null ){
			$this->data()->refreshTtl( $expire );
		}
		// 继承有效时间
		$this->dataId()->refreshTtl( $expire );
		$this->dataId()->refreshTtlOfRelevanceKeys( $expire );
		$this->session()->refreshTtl( $expire );
		$this->auth()->refreshTtl( $expire );
		//
		return $this;
	}

	/**
	 * 删除所有数据
	 * @return bool [true][必返]
	 */
	public function del():bool {
		$this->dataId()->del();
		$this->session()->del();
		$this->auth()->del();
		//
		$this->data()->del(); // 需要删除上面的子数据才可删除本数据
		//
		return true;
	}

	/**
	 * 获取数据id对象
	 * @return nameToken\DataId
	 */
	public function dataId(  ):nameToken\DataId {
		if( ! isset( $this->dataIdObj ) ){
			$this->dataIdObj = new nameToken\DataId( $this );
		}
		//
		return $this->dataIdObj;
	}

	/**
	 * 获取 accessToken 数据对象
	 * @return nameToken\Data
	 */
	public function data(  ):nameToken\Data {
		if( ! isset( $this->dataObj ) ){
			$this->dataObj = new nameToken\Data( $this );
		}
		//
		return $this->dataObj;
	}

	/**
	 * 获取权限对象
	 * @return nameToken\Auth
	 */
	public function auth(  ):nameToken\Auth {
		if( ! isset( $this->authObj ) ){
			$this->authObj = new nameToken\Auth( $this );
		}
		//
		return $this->authObj;
	}

	/**
	 * 获取session对象
	 * @return nameToken\Session
	 */
	public function session(  ):nameToken\Session {
		if( ! isset( $this->sessionObj ) ){
			$this->sessionObj = new nameToken\Session( $this );
		}
		//
		return $this->sessionObj;
	}

	// ===============================
	//
	//
	// 下面为静态方法
	//
	//
	// ===============================

	/**
	 * @param string $accessToken
	 * @return static
	 * @throws com\Lib\Err\BaseException\Throw_
	 * @throws com\Lib\Err\Exception
	 */
	public static function newClassOfUse( string $accessToken ):static {
		$accessTokenObj = new static( $accessToken );
		//
		$accessTokenData = $accessTokenObj->data()->get();
		if( false === $accessTokenData ){
			throw com\Lib\Err\Exception::makeOfNew( '', com\Lib\Err\Code::$sysDataNotExist );
		}
		// 检查是否需要刷新ttl
		if( ( $accessTokenData['expireTime'] - com\Lib\Fun\Time::getTime() ) < ( $accessTokenData['expire'] / 2 ) ){
			// 刷新
			$accessTokenObj->refreshTtl( $accessTokenData['expire'] );
		}
		//
		return $accessTokenObj;
	}

	/**
	 * 申请一个 accessToken
	 * @param array $param 参数数组 <br />
	 * ················· [可空](string)	$param['accessToken']		说明：指定 accessToken。[null][默认]创建一个新的 accessToken	<br />
	 * ················· [可空](int)	$param['expire']	说明：有效时间(秒)。默认:7200	<br />
	 * ················· [可空](int)	$param['dataId']	说明：指定的数据id。[null][默认]自动创建新的	<br />
	 * ················· [可空/多选_A/必须](unknown_type)	$param['AAAAA']	说明：AAAAA	<br />
	 * @return static accessToken 对象
	 * @throws
	 */
	public static function apply( array $param = [] ):static {
		// ...
		$param['accessToken'] = $param['accessToken'] ?? null;
		$param['expire'] = (int)( $param['expire'] ?? self::$defaultExpire );
		if( $param['expire'] <= 0 ) throw com\Lib\Err\Exception::makeOfNew( '', com\Lib\Err\Code::$sysFunParamError );
		$param['dataId'] = $param['dataId'] ?? null;
		// 是否指定 accessToken
		if( $param['accessToken'] !== null ){
			$accessTokenName = $param['accessToken'];
			if( strlen( $accessTokenName ) != 64 ) throw com\Lib\Err\Exception::makeOfNew( 'accessToken长度不符合', com\Lib\Err\Code::$sysFunParamError );
			$accessTokenKeyDbObj = com\Lib\Cache\KeyDB::use( 'sys.open.accessToken' )->param( $accessTokenName );
			if( $accessTokenKeyDbObj->has() ) throw com\Lib\Err\Exception::makeOfNew( 'accessToken已存在', com\Lib\Err\Code::$sysFunParamError );
		}else{
			$accessTokenName = self::createTokenStr();
			$accessTokenKeyDbObj = com\Lib\Cache\KeyDB::use( 'sys.open.accessToken' )->param( $accessTokenName );
			// 检查是否存在token
			if( $accessTokenKeyDbObj->has() ) return self::apply( $param );
		}
		// 构建数据
		$accessTokenData = self::buildTokenData( $accessTokenName, [
			'expire' => $param['expire'],
			'dataId' => $param['dataId'],
		] );
		// 生成
		$accessTokenKeyDbObj->set( $accessTokenData, $param['expire'] );
		// 返回
		return new static( $accessTokenName );
	}

	/**
	 * @param string $accessToken
	 * @return $this accessToken 对象
	 * @throws com\Lib\Err\BaseException\Throw_
	 */
/*	public static function applyOfToken( string $accessToken ):self {
		return self::apply( [
			'accessToken' => $accessToken,
		] ) ;
	}*/

	/**
	 * 构建 accessToken 数据
	 * @param string $accessToken
	 * @param array $param 参数数组 <br />
	 * ················· [可空](int)	$param['expire']	说明：有效时间(秒) 。默认:7200	<br />
	 * ················· [可空](int)	$param['dataId']	说明：指定的数据id。[null][默认]自动创建新的	<br />
	 * ················· [可空/多选_A/必须](unknown_type)	$param['AAAAA']	说明：AAAAA	<br />
	 * @return array
	 */
	private static function buildTokenData( string $accessToken, array $param = [] ):array {
		$param['expire'] = $param['expire'] ?? com\Lib\Fun\Time::getTime() + $param['expire'];
		$dataId = self::applyDataId( $accessToken, [
			'dataId' => $param['dataId'] ?? null,
			'expire' => $param['expire'],
		] );
		//
		return [
			'dataId' => $dataId,
			'createTime' => com\Lib\Fun\Time::getTime(), // 创建时间戳
			'expire' => $param['expire'] ?? self::$defaultExpire, // 有效时间
			'expireTime' => $param['expire'], // 到期时间戳
			'secretToken' => self::createSecretTokenStr( $accessToken ), // 数据加密秘钥
			'refreshToken' => self::createRefreshTokenStr( $accessToken ), // 刷新refresh
			'status' => nameToken\Code::$varStatus_Useing,
		];
	}

	/**
	 * 申请dataId
	 * @param string $accessToken
	 * @param array $param 参数数组 <br />
	 * ················· [可空](array)	$param['expire']	说明：有效时间(秒)	<br />
	 * ················· [可空](array)	$param['dataId']	说明：指定的数据id。[null][默认]自动创建新的	<br />
	 * ················· [可空/多选_A/必须](unknown_type)	$param['AAAAA']	说明：AAAAA	<br />
	 * @return string
	 */
	private static function applyDataId( string $accessToken, array $param = [] ):string {
		// ...
		$param['expire'] = $param['expire'] ?? self::$defaultExpire;
		$param['dataId'] = $param['dataId'] ?? null;
		//
		$dataId = $param['dataId'] ?? self::createDataIdStr();
		// 检查数据id是否存在
		$accessTokenDataIdKeyDbObj = com\Lib\Cache\KeyDB::use( 'sys.open.accessToken.dataId' )->param( $dataId );
		if( $param['dataId'] === null ){
			if( $accessTokenDataIdKeyDbObj->has() ) return self::applyDataId( $accessToken, $param );
		}
		//
		$accessTokenDataIdKeyDbObj->setOfPath( 'accessToken', $accessToken, $param['expire'] );
		//
		return $dataId;
	}

	/**
	 * 生成Token
	 * @param string $additionStr 散列加密字符（不建议为空，建议传入数据id或$app_id）
	 * @return string 返回一个64位的字符串
	 */
	private static function createTokenStr( string $additionStr = '' ):string {
		return md5(uniqid(md5(microtime(true)), true))
			. md5(uniqid(md5(microtime(true)), true) . $additionStr);
	}

	/**
	 * 生成RefreshToken
	 * @param string $additionStr 散列加密字符（不建议为空，建议传入数据id或$app_id）
	 * @return string 返回一个64位的字符串
	 */
	private static function createRefreshTokenStr( string $additionStr = '' ):string {
		return self::createTokenStr( $additionStr );
	}

	/**
	 * 生成SecretToken
	 * @param string $additionStr 散列加密字符（不建议为空，建议传入数据id或$app_id）
	 * @return string 返回一个64位的字符串
	 */
	private static function createSecretTokenStr( string $additionStr = '' ):string {
		return self::createTokenStr( $additionStr );
	}

	/**
	 * 生成数据关联id
	 * @param string $additionStr 散列加密字符（不建议为空，建议传入数据id或$app_id）
	 * @return string 返回一个32位的字符串
	 */
	private static function createDataIdStr( string $additionStr = '' ):string {
		return md5(uniqid(md5(microtime(true)), true) . $additionStr);
	}

}
